/***********************************************
 This file is part of redia-lib-utils-ios
 
 
 Copyright (c) 2015 Redia A/S
 
 Redia-library-app-ios is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 Redia-library-app-ios is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with redia-library-app-ios.  If not, see <http://www.gnu.org/licenses/>.
 
 *********************************************** */

#import "JSONRPCRequestHelper.h"
#import "JSONRPCClient.h"
#import "JSONRPCError.h"

@interface JSONRPCRequestHelper ()

@property (strong, nonatomic) NSMutableData *data;
@property (strong, nonatomic) JSONRPCClient *client;

@end


@implementation JSONRPCRequestHelper 

- (id)init
{
    self = [super init];
    if (self) {
    }
    return self;
}

+ (JSONRPCRequestHelper*)helperWithDelegate:(id<JSONRPCClientDelegate>)delegate client:(JSONRPCClient *)client
{
    JSONRPCRequestHelper *helper = [[JSONRPCRequestHelper alloc] init];
    
    if (helper) {
        helper.delegate = delegate;
        helper.client = client;
    }
    
    return helper;
}

+ (JSONRPCRequestHelper*)helperWithBlock:(CompletionHandlerBlock)block client:(JSONRPCClient *)client
{
    JSONRPCRequestHelper *helper = [[JSONRPCRequestHelper alloc] init];
    
    if (helper) {
        helper.completionHandlerBlock = block;
        helper.client = client;
    }
    
    return helper;
}

- (void)removeConnection:(NSURLConnection*)connection
{
    [JSONRPCClient removeConnection:connection];
    [self.client removeConnection:connection];
}

- (void)requestFailed:(NSInteger)requestId code:(NSInteger)errorCode message:(NSString *)message data:(id)data
{
    if (self.delegate) {
        [self.delegate JSONRPCClient:self.client requestFailed:requestId code:errorCode message:message data:data];
    }
    else {
        JSONRPCError *error = [JSONRPCError new];
        error.errorCode = errorCode;
        error.errorMessage = message;
        self.completionHandlerBlock(requestId, NO, error);
    }
}

- (void)requestSucceeded:(NSInteger)requestId result:(id)result
{
    if (self.delegate) {
        [self.delegate JSONRPCClient:self.client requestSucceeded:requestId result:result];
    }
    else {
        self.completionHandlerBlock(requestId, YES, result);
    }
}

#pragma mark - NSURLConnectionDelegate

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    NSInteger statusCode = [(NSHTTPURLResponse*)response statusCode];
    if (statusCode == 200) {
        self.data = [NSMutableData new];
    }
    else {
        NSLog(@"HTTP status code %li", (long)statusCode);
        self.data = nil;
        [self requestFailed:ERRORREQID
                       code:JSONRPC_SERVERERROR
                    message:[NSString stringWithFormat:@"HTTP status code %li", (long)statusCode]
                       data:nil];
        [self removeConnection:connection];
    }
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    NSLog(@"Error: HTTP request failed: %@", error);
    [self requestFailed:ERRORREQID code:JSONRPC_NETWORKERROR message:@"HTTP request failed." data:nil];
    [self removeConnection:connection];
}


#pragma mark - NSURLConnectionDataDelegate

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    [self.data appendData:data];
}

- (void)handleResponse:(NSDictionary *)response
{
    // Fetch the request id.
    id requestIdObject = [response objectForKey:@"id"];
    if (requestIdObject == nil || ![requestIdObject isKindOfClass:[NSNumber class]]) {
        NSLog(@"Got response without id. Protocol error.");
        [self requestFailed:ERRORREQID code:JSONRPC_UNKNOWNERROR message:@"Got response without id. Protocol error." data:nil];
        return;
    }
    NSInteger requestId = [requestIdObject integerValue];
    
    // Handle error conditions.
    id errorObject = [response objectForKey:@"error"];
    if (errorObject) {
        if ([errorObject isKindOfClass:[NSDictionary class]]) {
            NSDictionary *errorDict = (NSDictionary*)errorObject;
            NSNumber *errorCode = [errorDict objectForKey:@"code"];
            NSString *errorMessage = [errorDict objectForKey:@"message"];
            id errorData = [errorDict objectForKey:@"data"]; // TODO: we do not use data - should we?
            
            // Check that the required fields are there.
            if (errorCode == nil || ![errorCode isKindOfClass:[NSNumber class]] ||
                errorMessage == nil || ![errorMessage isKindOfClass:[NSString class]]) {
                NSLog(@"Error: An invalid error object was given by the server.");
                [self requestFailed:requestId code:JSONRPC_SERVERERROR message:@"An invalid error object was given by the server." data:nil];
                return;
            }
            
            // We have the required fields. Report the error.
            [self requestFailed:requestId code:[errorCode integerValue] message:errorMessage data:errorData];
            return;
        }
        else {
            // The error was not an object (nsdictionary).
            NSLog(@"Error: An invalid error object was given by the server.");
            [self requestFailed:requestId code:JSONRPC_SERVERERROR message:@"An invalid error object was given by the server." data:nil];
            return;
        }
    }
    
    // Handle valid results here.
    id resultObject = [response objectForKey:@"result"];
    if (resultObject) {
        [self requestSucceeded:requestId result:resultObject];
        return;
    }
    
    NSLog(@"Error: Neither error nor result object found in JSON RPC response.");
    [self requestFailed:requestId code:JSONRPC_PARSEERROR message:@"Neither error nor result object found in JSON RPC response." data:nil];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    if (self.data) {
        // Interpret the data as a JSON RPC response.
        NSError *error;
        id responseObject = [NSJSONSerialization JSONObjectWithData:self.data options:0 error:&error];
        if (responseObject != nil) {
            // Return the response if the delegate wants the raw data.
            if (self.delegate && [self.delegate respondsToSelector:@selector(JSONRPCClient:requestDoneRaw:)]) {
                [self.delegate JSONRPCClient:self.client requestDoneRaw:self.data];
            }
            
            // Check the JSON response.
            if ([responseObject isKindOfClass:[NSDictionary class]]) {
                // We have a response object.
                [self handleResponse:responseObject];
            }
            else if ([responseObject isKindOfClass:[NSArray class]]) {
                // We have a list of response objects.
                NSArray *responseArray = (NSArray*)responseObject;
                for (NSDictionary *response in responseArray) {
                    [self handleResponse:response];
                }
            }
            else {
                NSLog(@"Error: unknown return type from JSON RPC call.");
                [self requestFailed:ERRORREQID code:JSONRPC_UNKNOWNERROR message:@"Unknown return type from JSON-RPC call." data:nil];
            }
        }
        else {
            NSLog(@"Error: Failed to parse JSONRPC response. %@", error ? error : @"No error given");
            [self requestFailed:ERRORREQID code:JSONRPC_UNKNOWNERROR message:@"Failed to parse JSON-RPC response." data:nil];
        }
    }
    
    [self removeConnection:connection];
}

@end
